---
title: 从前端调用 Rust
---

Tauri 提供了一个简单而强大的 `command` 系统，用于从你的 Web 应用程序中调用 Rust 函数。
命令可以接受参数和返回值。它们也可以返回错误并且是 `async` 的。

## 基本的例子

命令在 `src-tauri/src/lib.rs` 文件中定义。要创建命令，只需添加一个函数并用 `#[tauri::command]` 标注它。

```rust title="src-tauri/src/lib.rs"
#[tauri::command]
fn my_custom_command() {
	println!("I was invoked from JavaScript!");
}
```

你必须向构建函数提供命令列表，如下所示。

```rust title="src-tauri/src/lib.rs" ins={4}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
	tauri::Builder::default()
		.invoke_handler(tauri::generate_handler![my_custom_command])
		.run(tauri::generate_context!())
		.expect("error while running tauri application");
}
```

现在，你可以从 JavaScript 代码中调用该命令。

```javascript
// 当使用 Tauri API 的 npm 包时
import { invoke } from '@tauri-apps/api/core';

// 使用 Tauri 全局脚本时（如果不使用 npm 包）
// 确保在 `tauri.conf.json` 中将 `app.withGlobalTauri` 设置为 true。
const invoke = window.__TAURI__.invoke;

// 使用命令
invoke('my_custom_command');
```

## 传递参数

你的命令处理程序可以接受以下参数。

```rust
#[tauri::command]
fn my_custom_command(invoke_message: String) {
	println!("I was invoked from JavaScript, with this message: {}", invoke_message);
}
```

参数应该作为一个带有 camelCase 键的 JSON 对象传递。

```javascript
invoke('my_custom_command', { invokeMessage: 'Hello!' });
```

参数可以是任何类型，只要它们实现了 [`serde::Deserialize`]。

请注意，当在 Rust 中使用 snake_case 声明参数时，在 JavaScript 中参数会被转换为 camelCase。
要在 JavaScript 中使用 snake_case，你必须在 `tauri::command` 语句中声明它。

```rust
#[tauri::command(rename_all = "snake_case")]
fn my_custom_command(invoke_message: String) {
	println!("I was invoked from JavaScript, with this message: {}", invoke_message);
}
```

对应的 JavaScript 代码。

```javascript
invoke('my_custom_command', { invoke_message: 'Hello!' });
```

## 返回数据

命令处理程序也可以返回数据。

```rust
#[tauri::command]
fn my_custom_command() -> String {
	"Hello from Rust!".into()
}
```

`invoke` 函数返回一个 promise，其 resolve 接收一个返回值。

```javascript
invoke('my_custom_command').then((message) => console.log(message));
```

返回的数据可以是任何类型，只要它实现了 [`serde::Serialize`]。

## 错误处理

如果你的处理程序可能失败并且需要能够返回一个错误，让函数返回一个 `Result`。

```rust
#[tauri::command]
fn my_custom_command() -> Result<String, String> {
	// If something fails
	Err("This failed!".into())
	// If it worked
	Ok("This worked!".into())
}
```

如果命令返回错误，promise 将 reject，否则 resolve。

```javascript
invoke('my_custom_command')
  .then((message) => console.log(message))
  .catch((error) => console.error(error));
```

如上所述，从命令返回的所有内容都必须实现 [`serde::Serialize`]，包括错误。
如果您正在处理来自 Rust 的 std 库或外部 crate 的错误类型，则可能会出现问题，因为大多数错误类型都没有实现它。
一般情况下，你可以使用 `map_err` 将这些错误转换为 `String`。

```rust
#[tauri::command]
fn my_custom_command() -> Result<(), String> {
	// 这将返回一个错误
	std::fs::File::open("path/that/does/not/exist").map_err(|err| err.to_string())?;
	// 成功返回空
	Ok(())
}
```

因为这不是很符合习惯，你可能想要创建自己的错误类型来实现 `serde::Serialize`。在下面的例子中，我们使用 [`thiserror`] 创建错误类型。
它允许你通过派生 `thiserror::Error` 特征将枚举转换为错误类型。你可以查阅它的文档了解更多细节。

```rust
// 创建 error 类型，表示程序中可能出现的所有错误
#[derive(Debug, thiserror::Error)]
enum Error {
	#[error(transparent)]
	Io(#[from] std::io::Error)
}

// 我们必须手动实现 serde::Serialize
impl serde::Serialize for Error {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: serde::ser::Serializer,
	{
		serializer.serialize_str(self.to_string().as_ref())
	}
}

#[tauri::command]
fn my_custom_command() -> Result<(), Error> {
	// 这将返回一个错误
	std::fs::File::open("path/that/does/not/exist")?;
	// 成功返回空
	Ok(())
}
```

自定义错误类型的优点是明列所有可能的错误，以便读者快速识别可能发生的错误。这为别人（和你自己）在以后审查和重构代码时节省了大量的时间。<br/>
它还让你可以完全控制错误类型被序列化的方式。在上面的例子中，我们简单地将错误消息作为字符串返回，但是你可以为每个错误分配一个类似于 C 的代码，这样你可以更容易地将它映射到一个看起来类似的 TypeScript 错误枚举。

## 异步命令

异步函数在 Tauri 中有利于执行繁重的工作，不会导致 UI 冻结或减慢。

:::note

异步命令使用 [`async_runtime::spawn`] 在单独的线程上执行。
没有 _async_ 关键字的命令将在主线程上执行，除非定义了 _#[tauri::command(async)]_。

:::

**如果你的命令需要异步运行，只需将其声明为 `async`。**

:::caution

使用 Tauri 创建异步函数时需要小心目前，你不能简单地在异步函数的签名中包含借用的参数。一些常见的类型示例是 `&str` 和 `State<'_, Data>`。
这里追踪了这个限制： https://github.com/tauri-apps/tauri/issues/2533 和解决方法如下所示。

:::

在使用借用的类型时，必须进行额外的更改。这是你的两个主要选择。

**选项 1**：将类型（如 `&str`）转换为不允许借用的类似类型（如 `String`）。 这可能不适用于所有类型，例如 `State<'_, Data>`。

_示例：_

```rust
// 使用 String 而不是 &str 声明异步函数，因为 &str 是借用的，因此不受支持
#[tauri::command]
async fn my_custom_command(value: String) -> String {
	// 调用另一个异步函数并等待它完成
	some_async_function().await;
	value
}
```

**选项 2**：将返回类型包装在一个 [`Result`] 中。这个实现起来有点难，但应该适用于所有类型。

使用返回类型 `Result<a, b>`，将 `a` 替换为你想要返回的类型，或者如果你不想返回任何类型，则使用 `()` ，如果发生错误，则将 `b` 替换为错误类型，或者如果你不想返回任何可选错误，则使用 `()`。例如：

- `Result<String, ()>` 返回一个字符串，并且没有错误。
- `Result<(), ()>` 什么都不返回。
- `Result<bool, Error>` 返回一个布尔值或一个错误，如上面的[错误处理](#错误处理)部分所示。

_示例：_

```rust
// 返回一个 Result<String, ()> 来绕过借用问题
#[tauri::command]
async fn my_custom_command(value: &str) -> Result<String, ()> {
	// 调用另一个异步函数并等待它完成
	some_async_function().await;
	// 注意，现在必须将返回值包装在 `Ok()` 中。
	Ok(format!(value))
}
```

#### 从 JavaScript 调用

因为在 JavaScript 中调用这个命令会返回一个 promise，所以它的工作方式和其他命令一样。

```javascript
invoke('my_custom_command', { value: 'Hello, Async!' }).then(() =>
  console.log('Completed!')
);
```

## 在命令中访问 WebviewWindow

命令可以访问调用消息的 `WebviewWindow` 实例。

```rust title="src-tauri/src/lib.rs"
#[tauri::command]
async fn my_custom_command(webview_window: tauri::WebviewWindow) {
	println!("WebviewWindow: {}", webview_window.label());
}
```

## 在命令中访问 Window

命令可以访问调用消息的 `Window` 实例。

```rust title="src-tauri/src/lib.rs"
#[tauri::command]
async fn my_custom_command(window: tauri::Window) {
	println!("Window: {}", window.label());
}
```

你也可以从 `WebviewWindow` 中访问 `Window` 实例:

```rust title="src-tauri/src/lib.rs"
#[tauri::command]
async fn my_custom_command(webview_window: tauri::WebviewWindow) {
	let window: tauri::Window = webview_window.as_ref().window();
	println!("Window: {}", window.label());
}
```

## 在命令中访问 AppHandle

命令可以访问 `AppHandle` 实例。

```rust title="src-tauri/src/lib.rs"
#[tauri::command]
async fn my_custom_command(app_handle: tauri::AppHandle) {
	let app_dir = app_handle.path_resolver().app_dir();
	use tauri::GlobalShortcutManager;
	app_handle.global_shortcut_manager().register("CTRL + U", move || {});
}
```

## 访问托管状态

Tauri 可以使用 `Tauri::Builder` 上的 `manage` 函数来管理状态。
可以使用 `tauri::State` 命令访问状态。

```rust title="src-tauri/src/lib.rs"
struct MyState(String);

#[tauri::command]
fn my_custom_command(state: tauri::State<MyState>) {
	assert_eq!(state.0 == "some state value", true);
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
	tauri::Builder::default()
		.manage(MyState("some state value".into()))
		.invoke_handler(tauri::generate_handler![my_custom_command])
		.run(tauri::generate_context!())
		.expect("error while running tauri application");
}
```

## 创建多个命令

`tauri::generate_handler!` 宏接受一个命令数组作为参数，
为了注册多个命令，你不能多次调用 invoke_handler，
只有最后一次调用才会被使用，你必须将每个命令传递给 `tauri::generate_handler!` 的单次调用。

```rust title="src-tauri/src/lib.rs"
#[tauri::command]
fn cmd_a() -> String {
	"Command a"
}
#[tauri::command]
fn cmd_b() -> String {
	"Command b"
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
	tauri::Builder::default()
		.invoke_handler(tauri::generate_handler![cmd_a, cmd_b])
		.run(tauri::generate_context!())
		.expect("error while running tauri application");
}
```

## 完整的示例

上面的任何一个或所有功能都可以组合起来。

```rust title="src-tauri/src/lib.rs"
struct Database;

#[derive(serde::Serialize)]
struct CustomResponse {
	message: String,
	other_val: usize,
}

async fn some_other_function() -> Option<String> {
	Some("response".into())
}

#[tauri::command]
async fn my_custom_command(
	window: tauri::Window,
	number: usize,
	database: tauri::State<'_, Database>,
) -> Result<CustomResponse, String> {
	println!("Called from {}", window.label());
	let result: Option<String> = some_other_function().await;
	if let Some(message) = result {
		Ok(CustomResponse {
			message,
			other_val: 42 + number,
		})
	} else {
		Err("No result".into())
	}
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
	tauri::Builder::default()
		.manage(Database {})
		.invoke_handler(tauri::generate_handler![my_custom_command])
		.run(tauri::generate_context!())
		.expect("error while running tauri application");
}
```

```javascript
import { invoke } from '@tauri-apps/api/core';

// 从 JavaScript 调用
invoke('my_custom_command', {
  number: 42,
})
  .then((res) =>
    console.log(`Message: ${res.message}, Other Val: ${res.other_val}`)
  )
  .catch((e) => console.error(e));
```

[`async_runtime::spawn`]: https://docs.rs/tauri/2.0.0/tauri/async_runtime/fn.spawn.html
[`serde::serialize`]: https://docs.serde.rs/serde/trait.Serialize.html
[`serde::deserialize`]: https://docs.serde.rs/serde/trait.Deserialize.html
[`thiserror`]: https://github.com/dtolnay/thiserror
[`result`]: https://doc.rust-lang.org/std/result/index.html
